from django.views.generic.list_detail import object_list, object_detail
import wadofstuff.django.views
from xml.dom import minidom
from django.views.generic.create_update import *
from django.http import HttpResponse, HttpResponseRedirect
from django.template import RequestContext, Context, loader
from django.forms.models import inlineformset_factory
from openlabyrinth.models import *
from openlabyrinth.forms import *
from openlabyrinth.views import *
from django.contrib.auth import authenticate, login, logout
import time
import re
import math
import socket
import shutil
import tempfile, zipfile, os
from datetime import datetime
from django.core.servers.basehttp import FileWrapper
from django.contrib.sessions.models import Session
from django.contrib.sessions.backends.db import SessionStore
from django.shortcuts import render_to_response
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.forms import ValidationError

import logging
LOG_FILENAME = '/tmp/django.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)

loginForm = None

## 
## Initialization routine for OpenLabyrinth
##
## This function handles logins for any view, and instantiates
## the login form so that it can be used by every view. 
## 

def initialize(view):
    
    def do_initialize(request, **arguments):
        
        # Set the session to expire when the browser closes
        request.session.set_expiry(0)
        
        global loginForm
        loginForm = LoginForm()
        
        # If the user is trying to login, handle the request
        if request.method == "POST" and request.POST.has_key('username'):
            username = request.POST['username']
            password = request.POST['password']
            user = authenticate(username=username, password=password)
            if user is not None and user.is_active:
                login(request, user)
            # Re-direct to the node listing view.
            return HttpResponseRedirect(reverse('ol-home'))
        
        return view(request, **arguments)
        
    return do_initialize
    

## =============================================================================
## Administration Views
## 
##   These views are responsible for drawing the custom administrative interface
##   used to create, read, update and delete (CRUD) some of the back-end data 
##   structures available. Not every data structure has these views as some are  
##   edited using form composition, and as a result, are nested within other objects.
## =============================================================================

@login_required
def admin_dashboard(request, **arguments):
    return render_to_response('admin_dashboard.html',
        {},
        context_instance=RequestContext(request))

## 
## Visual Editor (Administration)
## 
## This view renders the visual editor.
## 

@login_required
def admin_visualEditor(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    return render_to_response('admin_visual_editor.html',
        {'labyrinth': labyrinth},
        context_instance=RequestContext(request))

## 
## Create Node (Administration)
## 
## This view is used to create a new node.
## 

@login_required
def admin_createNode(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Generate the inline formset class we'll use to edit node variances.
    InlineNodeVarianceFormSet = inlineformset_factory(Node, NodeVariance)
    
    # Generate the inline formset class we'll use to edit node conditions.
    InlineNodeConditionFormSet = inlineformset_factory(Node, NodeCondition, fk_name='node', fields=['nodes', 'operation', 'message'])
    
    # If the user is submitting a node for creation.
    if request.method == 'POST':
        form = NodeForm(request.POST)
        
        # Restrict the available avatars to those from this labyrinth.
        form.fields['avatar'].queryset = Avatar.objects.filter(labyrinth=labyrinth)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newNode = form.save(commit=False)
            newNode.labyrinth = labyrinth
            
            # Get the inline form data using the new node as a reference.
            inline_variance_formset = InlineNodeVarianceFormSet(request.POST, instance=newNode)
            inline_condition_formset = InlineNodeConditionFormSet(request.POST, instance=newNode)
        
            # Restrict the inline variance form's available counters to those from this labyrinth.
            for inline_form in inline_variance_formset.forms:
                inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
        
            # Restrict the inline condition form's available nodes to those from this labyrinth.
            for inline_form in inline_condition_formset.forms:
                inline_form.fields['nodes'].queryset = Node.objects.filter(labyrinth=labyrinth)
            
            # If the inline variances and conditions are valid, save the node and then the variances.
            if inline_variance_formset.is_valid() and inline_condition_formset.is_valid():
                newNode.save()
                inline_variance_formset.save()
                inline_condition_formset.save()
                
                # Let the user know that the operation was successful.
                request.user.message_set.create(message="The node was created successfully.")
            
                # Re-direct to the node listing view.
                return HttpResponseRedirect(reverse('ol-readNodes', kwargs={'labyrinthID': labyrinth.id}))
        else:
            # The node form isn't valid, so create the inline forms using the data that
            # was submitted. Inline form errors will be displayed.
            inline_variance_formset = InlineNodeVarianceFormSet(request.POST)
            inline_condition_formset = InlineNodeConditionFormSet(request.POST)
    else:
        form = NodeForm()
        inline_variance_formset = InlineNodeVarianceFormSet()
        inline_condition_formset = InlineNodeConditionFormSet()
        
    # Restrict the available avatars to those from this labyrinth.
    form.fields['avatar'].queryset = Avatar.objects.filter(labyrinth=labyrinth)
        
    # Restrict the inline variance form's available counters to those from this labyrinth.
    for inline_form in inline_variance_formset.forms:
        inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
        
    # Restrict the inline condition form's available nodes to those from this labyrinth.
    for inline_form in inline_condition_formset.forms:
        inline_form.fields['nodes'].queryset = Node.objects.filter(labyrinth=labyrinth)
    
    inline_formsets = ({'form': inline_variance_formset, 'headers':
        ('', 'Counter', 'Operation', 'Value', '', 'Delete'), 'label': 'Variances'},{'form': inline_condition_formset, 'headers':
        ('Nodes', 'Operation', 'Message', '', 'Delete', ''), 'label': 'Condition'},)
    
    return render_to_response('admin_object.html',
        {'form': form, 'inline_formsets': inline_formsets, 'tinyMCE' : True, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Nodes (Administration)
## 
## This view is used to display the nodes in a given labyrinth.
## 

@login_required
def admin_readNodes(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the nodes of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Node.objects.filter(labyrinth=labyrinthID),
        template_name='admin_node_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Node (Administration)
## 
## This view is used to update an existing node.
## 

@login_required
def admin_updateNode(request, **arguments):
    
    # Get the node object so that the form can be bound to its details.
    node = Node.objects.get(id=arguments.get('nodeID'))
    
    # Generate the inline formset class we'll use to edit node variances.
    InlineNodeVarianceFormSet = inlineformset_factory(Node, NodeVariance)
    
    # Generate the inline formset class we'll use to edit node conditions.
    InlineNodeConditionFormSet = inlineformset_factory(Node, NodeCondition, fk_name='node', fields=['nodes', 'operation', 'message'])
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting a node for updating.
    if request.method == 'POST':
        form = NodeForm(request.POST, request.FILES, instance=node)
        
        # Restrict the available avatars to those from this labyrinth.
        form.fields['avatar'].queryset = Avatar.objects.filter(labyrinth=labyrinth)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newNode = form.save(commit=False)
            newNode.labyrinth = labyrinth
            
            # Get the inline form data using the new node as a reference.
            inline_variance_formset = InlineNodeVarianceFormSet(request.POST, instance=newNode)
            inline_condition_formset = InlineNodeConditionFormSet(request.POST, instance=newNode)
        
            # Restrict the inline variance form's available counters to those from this labyrinth.
            for inline_form in inline_variance_formset.forms:
                inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
        
            # Restrict the inline condition form's available nodes to those from this labyrinth.
            for inline_form in inline_condition_formset.forms:
                inline_form.fields['nodes'].queryset = Node.objects.filter(labyrinth=labyrinth).exclude(id=node.id)
            
            # If the inline variances and conditions are valid, save the node and then the variances.
            if inline_variance_formset.is_valid() and inline_condition_formset.is_valid():
                newNode.save()
                inline_variance_formset.save()
                inline_condition_formset.save()
                
                # Let the user know that the operation was successful.
                request.user.message_set.create(message="The node was updated successfully.")
            
                # Re-direct to the node listing view.
                return HttpResponseRedirect(reverse('ol-readNodes', kwargs={'labyrinthID':labyrinth.id}))
        else:
            # The node form isn't valid, so create the inline forms using the data that
            # was submitted and the original node object. Inline form errors will be displayed.
            inline_variance_formset = InlineNodeVarianceFormSet(request.POST, instance=node)
            inline_condition_formset = InlineNodeConditionFormSet(request.POST, instance=node)
    else:
        form = NodeForm(instance=node)
        inline_variance_formset = InlineNodeVarianceFormSet(instance=node)
        inline_condition_formset = InlineNodeConditionFormSet(instance=node)
    
    # Add some information used to draw the inline node variance form's table.
    inline_formset_headers = ('', 'Counter', 'Operation', 'Value', '', 'Delete')
    inline_formset_label = 'Variances'
        
    # Restrict the available avatars to those from this labyrinth.
    form.fields['avatar'].queryset = Avatar.objects.filter(labyrinth=labyrinth)
        
    # Restrict the inline variance form's available counters to those from this labyrinth.
    for inline_form in inline_variance_formset.forms:
        inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
        
    # Restrict the inline condition form's available nodes to those from this labyrinth.
    for inline_form in inline_condition_formset.forms:
        inline_form.fields['nodes'].queryset = Node.objects.filter(labyrinth=labyrinth).exclude(id=node.id)
    
    inline_formsets = ({'form': inline_variance_formset, 'headers':
        ('', 'Counter', 'Operation', 'Value', '', 'Delete'), 'label': 'Variances'},{'form': inline_condition_formset, 'headers':
        ('Nodes', 'Operation', 'Message', '', 'Delete', ''), 'label': 'Condition'},)
    
    return render_to_response('admin_object.html',
        {'form': form, 'inline_formsets': inline_formsets, 'tinyMCE' : True, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

## 
## Delete Node (Administration)
## 
## This view is used to delete an existing node.
## 

@login_required
def admin_deleteNode(request, **arguments):
    
    # Get the node so that the generic view can retrieve its details.
    node = Node.objects.get(id=arguments.get('nodeID'))
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # If this is the labyrinth's root node, clear its reference.
    if labyrinth.root_node == node:
        labyrinth.root_node = None
        labyrinth.save()

    # Delete any variance objects associated with this node.
    NodeVariance.objects.filter(node=node).delete()
    
    # Delete any condition objects associated with this node.
    NodeCondition.objects.filter(node=node).delete()
    
    if node.section != None and node.section_index != None:
        # Store the section and index so we can still access them after the node is deleted.
        section = node.section
        index = node.section_index
            
        # Delete the node and decrement any other nodes'
        # indices to fill in the gap left behind.
        node.delete()
        for node in Node.objects.filter(section=section).filter(section_index__gt=index).order_by('section_index'):
            node.section_index -= 1
            node.save()

    # Re-direct to the node listing page.
    return HttpResponseRedirect(reverse('ol-readNodes', kwargs={'labyrinthID': labyrinth.id}))

## 
## Set Root Node (Administration)
## 
## This view is used to set an existing node as the labyrinth root.
## 

@login_required
def admin_setRootNode(request, **arguments):
    
    # Get the node so that we can set it as the root of the labyrinth.
    node = Node.objects.get(id=arguments.get('nodeID'))
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Make sure that this node belongs in this labyrinth. If it does, save it!
    if node.labyrinth == labyrinth:
        labyrinth.root_node = node
        labyrinth.save()
                
    # Let the user know that the operation was successful.
    request.user.message_set.create(message="The root node was updated successfully.")
            
    # Re-direct to the node listing view.
    return HttpResponseRedirect(reverse('ol-readNodes', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create Link (Administration)
## 
## This view is used to create a new link. We modify the form field querysets
## for both source and destination so that only nodes in the current labyrinth
## are available; this prevents cross-labyrinth linking.
## 

@login_required
def admin_createLink(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the node object that we're working in.
    node = Node.objects.get(id=arguments.get('nodeID'))
    
    # Generate the inline formset class we'll use to edit link variances.
    InlineLinkVarianceFormSet = inlineformset_factory(Link, LinkVariance)
    
    # If the user is submitting a link for creation. There may
    # also be a link variance that has been sent with the new link.
    if request.method == 'POST':
        form = LinkForm(request.POST, request.FILES)
        
        # Restrict the available nodes to those from this labyrinth.
        form.fields['destination'].queryset = Node.objects.filter(labyrinth=labyrinth)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in.
        if form.is_valid():
            newLink = form.save(commit=False)
            newLink.labyrinth = labyrinth
            newLink.source = node
            
            # Get the inline form data using the new link as a reference.
            inline_formset = InlineLinkVarianceFormSet(request.POST, instance=newLink)
        
            # Restrict the inline form's available counters to those from this labyrinth.
            for inline_form in inline_formset.forms:
                inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
            
            # If the inline variances are valid, save the link and then the variances.
            if inline_formset.is_valid():
                newLink.save()
                inline_formset.save()
                
                # Let the user know that the operation was successful.
                request.user.message_set.create(message="The link was created successfully.")
            
                # Re-direct to the link listing view.
                return HttpResponseRedirect(reverse('ol-readLinks', kwargs={'labyrinthID':labyrinth.id, 'nodeID':node.id}))
        else:
            # The link form isn't valid, so create the inline form using the data that
            # was submitted. Inline form errors will be displayed.
            inline_formset = InlineLinkVarianceFormSet(request.POST)
    else:
        form = LinkForm()
        inline_formset = InlineLinkVarianceFormSet()
    
    # Add some information used to draw the inline link variance form's table.
    inline_formset_headers = ('', 'Counter', 'Operation', 'Value', '', 'Delete')
    inline_formset_label = 'Variances'
        
    # Restrict the available nodes to those from this labyrinth.
    form.fields['destination'].queryset = Node.objects.filter(labyrinth=labyrinth)
        
    # Restrict the inline form's available counters to those from this labyrinth.
    for inline_form in inline_formset.forms:
        inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
    
    inline_formsets = ({'form': inline_formset, 'headers': inline_formset_headers, 'label': inline_formset_label},)
    
    return render_to_response('admin_object.html',
        {'form': form, 'inline_formsets': inline_formsets, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Links (Administration)
## 
## This view is used to display the links in a given labyrinth.
## 

@login_required
def admin_readLinks(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')
    
    # Get the node object that we're working in.
    node = Node.objects.get(id=arguments.get('nodeID'))
    
    # Get the links that originate from this node.
    links = Link.objects.filter(labyrinth=labyrinthID, source=node)
    
    # Call on a generic view to generate the listing, using the links of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=links,
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Link (Administration)
## 
## This view is used to update an existing link. We modify the form field querysets
## for both source and destination so that only nodes in the current labyrinth
## are available; this prevents cross-labyrinth linking.
## 

@login_required
def admin_updateLink(request, **arguments):
    
    # Get the link object so that the form can be bound to its details.
    link = Link.objects.get(id=arguments.get('linkID'))
    
    # Generate the inline formset class we'll use to edit link variances.
    InlineLinkVarianceFormSet = inlineformset_factory(Link, LinkVariance)
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the node object that we're working in.
    node = Node.objects.get(id=arguments.get('nodeID'))
    
    # If the user is submitting a link for updating.
    if request.method == 'POST':
        form = LinkForm(request.POST, request.FILES, instance=link)
        
        # Restrict the available nodes to those from this labyrinth.
        form.fields['destination'].queryset = Node.objects.filter(labyrinth=labyrinth)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newLink = form.save(commit=False)
            newLink.labyrinth = labyrinth
            newLink.source = node
            
            # Get the inline form data using the new link as a reference.
            inline_formset = InlineLinkVarianceFormSet(request.POST, instance=newLink)
        
            # Restrict the inline form's available counters to those from this labyrinth.
            for inline_form in inline_formset.forms:
                inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
            
            # If the inline variances are valid, save the link and then the variances.
            if inline_formset.is_valid():
                newLink.save()
                inline_formset.save()
                
                # Let the user know that the operation was successful.
                request.user.message_set.create(message="The link was updated successfully.")
            
                # Re-direct to the link listing view.
                return HttpResponseRedirect(reverse('ol-readLinks', kwargs={'labyrinthID':labyrinth.id, 'nodeID':node.id}))
        else:
            # The link form isn't valid, so create the inline form using the data that
            # was submitted and the original link object. Inline form errors will be displayed.
            inline_formset = InlineLinkVarianceFormSet(request.POST, instance=link)
    else:
        form = LinkForm(instance=link)
        inline_formset = InlineLinkVarianceFormSet(instance=link)
    
    # Add some information used to draw the inline link variance form's table.
    inline_formset_headers = ('', 'Counter', 'Operation', 'Value', '', 'Delete')
    inline_formset_label = 'Variances'
        
    # Restrict the available nodes to those from this labyrinth.
    form.fields['destination'].queryset = Node.objects.filter(labyrinth=labyrinth)
        
    # Restrict the inline form's available counters to those from this labyrinth.
    for inline_form in inline_formset.forms:
        inline_form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
    
    inline_formsets = ({'form': inline_formset, 'headers': inline_formset_headers, 'label': inline_formset_label},)
    
    return render_to_response('admin_object.html',
        {'form': form, 'inline_formsets': inline_formsets, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

## 
## Delete Link (Administration)
## 
## This view is used to delete an existing link.
## 

@login_required
def admin_deleteLink(request, **arguments):
    
    # Get the link ID so that the generic view can retrieve its details.
    linkID = arguments.get('linkID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the node object that we're working in.
    node = Node.objects.get(id=arguments.get('nodeID'))
    
    # Delete any variance objects associated with this link.
    LinkVariance.objects.filter(link=Link.objects.get(id=linkID)).delete()

    # Call on a generic view to generate the listing, using the links of this
    # labyrinth and the custom template we've defined.
    return delete_object(request,
        model=Link,
        object_id=linkID,
        login_required=True,
        post_delete_redirect=reverse('ol-readLinks', kwargs={'labyrinthID':labyrinth.id, 'nodeID':node.id}))

## 
## Create Counter (Administration)
## 
## This view is used to create a new counter.
## 

@login_required
def admin_createCounter(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting a counter for creation.
    if request.method == 'POST':
        form = CounterForm(request.POST)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newCounter = form.save(commit=False)
            newCounter.labyrinth = labyrinth
            newCounter.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The counter was created successfully.")
            
            # Re-direct to the node listing view.
            return HttpResponseRedirect(reverse('ol-readCounters', kwargs={'labyrinthID':labyrinth.id}))
    else:
        form = CounterForm()

    return render_to_response('admin_object.html',
        {'form': form, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Counters (Administration)
## 
## This view is used to display the counters in a given labyrinth.
## 

@login_required
def admin_readCounters(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the counters of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Counter.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Counter (Administration)
## 
## This view is used to update an existing counter.
## 

@login_required
def admin_updateCounter(request, **arguments):
    
    # Get the counter ID so that the generic view can retrieve its details.
    counterID = arguments.get('counterID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the node data
    # and the custom template we've defined. An extra context is used
    # to include the JavaScript library for TinyMCE initialization.
    return update_object(request,
        form_class=CounterForm,
        object_id=counterID,
        login_required=True,
        post_save_redirect=reverse('ol-readCounters', kwargs={'labyrinthID':labyrinth.id}),
        extra_context={'labyrinth': labyrinth},
        template_name='admin_object.html')

## 
## Delete Counter (Administration)
## 
## This view is used to delete an existing counter.
## 

@login_required
def admin_deleteCounter(request, **arguments):
    
    # Get the counter ID so that the generic view can retrieve its details.
    counterID = arguments.get('counterID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # Call on a generic view to generate the listing, using the
    # counters of this labyrinth and the custom template we've defined.
    return delete_object(request,
        model=Counter,
        object_id=counterID,
        login_required=True,
        post_delete_redirect=reverse('ol-readCounters', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create Feedback (Administration)
## 
## This view is used to create a new feedback object.
## 

@login_required
def admin_createFeedback(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting a feedback object for creation.
    if request.method == 'POST':
        form = FeedbackForm(request.POST)
        
        # Restrict the available nodes and counters to those from this labyrinth.
        form.fields['node'].queryset = Node.objects.filter(labyrinth=labyrinth)
        form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newFeedback = form.save(commit=False)
            newFeedback.labyrinth = labyrinth
            newFeedback.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The feedback was created successfully.")
            
            # Re-direct to the asset listing view.
            return HttpResponseRedirect(reverse('ol-readFeedback', kwargs={'labyrinthID':labyrinth.id}))
    else:
        form = FeedbackForm()
        
        # Restrict the available nodes and counters to those from this labyrinth.
        form.fields['node'].queryset = Node.objects.filter(labyrinth=labyrinth)
        form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)

    return render_to_response('admin_feedback.html',
        {'form': form, 'tinyMCE' : True, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Feedback (Administration)
## 
## This view is used to display the feedback objects in a given labyrinth.
## 

@login_required
def admin_readFeedback(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the feedback
    # objects of this labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Feedback.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID), 'object_name_plural': 'Feedback'})

## 
## Update Feedback (Administration)
## 
## This view is used to update an existing feedback object.
## 

@login_required
def admin_updateFeedback(request, **arguments):
    
    # Get the feedback ID so that the generic view can retrieve its details.
    feedback = Feedback.objects.get(id=arguments.get('feedbackID'))
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting a link for updating.
    if request.method == 'POST':
        form = FeedbackForm(request.POST, instance=feedback)
        
        # Restrict the available nodes and counters to those from this labyrinth.
        form.fields['node'].queryset = Node.objects.filter(labyrinth=labyrinth)
        form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newFeedback = form.save(commit=False)
            newFeedback.labyrinth = labyrinth
            newFeedback.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The feedback was updated successfully.")
            
            # Re-direct to the link listing view.
            return HttpResponseRedirect(reverse('ol-readFeedback', kwargs={'labyrinthID':labyrinth.id}))
            
    else:
        form = FeedbackForm(instance=feedback)
        
        # Restrict the available nodes and counters to those from this labyrinth.
        form.fields['node'].queryset = Node.objects.filter(labyrinth=labyrinth)
        form.fields['counter'].queryset = Counter.objects.filter(labyrinth=labyrinth)
    
    return render_to_response('admin_feedback.html',
        {'form': form, 'tinyMCE' : True, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

## 
## Delete Feedback (Administration)
## 
## This view is used to delete an existing feedback object.
## 

@login_required
def admin_deleteFeedback(request, **arguments):
    
    # Get the feedback ID so that the generic view can retrieve its details.
    feedbackID = arguments.get('feedbackID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # Call on a generic view to generate the listing, using the
    # assets of this labyrinth and the custom template we've defined.
    return delete_object(request,
        model=Feedback,
        object_id=feedbackID,
        login_required=True,
        post_delete_redirect=reverse('ol-readFeedback', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create Question (Administration)
## 
## This view is used to create a new counter.
## 

@login_required
def admin_createQuestion(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting a question for creation.
    if request.method == 'POST':
        form = QuestionForm(request.POST)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newQuestion = form.save(commit=False)
            newQuestion.labyrinth = labyrinth
            newQuestion.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The question was created successfully.")
            
            # Re-direct to the node listing view.
            return HttpResponseRedirect(reverse('ol-readQuestions', kwargs={'labyrinthID':labyrinth.id}))
    else:
        form = QuestionForm()

    return render_to_response('admin_object.html',
        {'form': form, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Questions (Administration)
## 
## This view is used to display the questions in a given labyrinth.
## 

@login_required
def admin_readQuestions(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the counters of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Question.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Question (Administration)
## 
## This view is used to update an existing question.
## 

@login_required
def admin_updateQuestion(request, **arguments):
    
    # Get the question ID so that the generic view can retrieve its details.
    questionID = arguments.get('questionID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing.
    return update_object(request,
        form_class=QuestionForm,
        object_id=questionID,
        login_required=True,
        post_save_redirect=reverse('ol-readQuestions', kwargs={'labyrinthID':labyrinth.id}),
        extra_context={'labyrinth': labyrinth},
        template_name='admin_object.html')

## 
## Delete Question (Administration)
## 
## This view is used to delete an existing question.
## 

@login_required
def admin_deleteQuestion(request, **arguments):
    
    # Get the question ID so that the generic view can retrieve its details.
    questionID = arguments.get('questionID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # Call on a generic view to generate the listing, using the
    # questions of this labyrinth and the custom template we've defined.
    return delete_object(request,
        model=Question,
        object_id=questionID,
        login_required=True,
        post_delete_redirect=reverse('ol-readQuestions', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create Element (Administration)
## 
## This view is used to create a new element.
## 

@login_required
def admin_createElement(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the element type from the URL
    elementType = arguments.get('elementType')
    
    createUrl = reverse('ol-createElement', kwargs={'labyrinthID': labyrinth.id})
    
    if not elementType:
        elementType = 'vpd_text'
    
    initialData = {'vpd_element_type': elementType}
    
    # Figure out what kind of form we're working with and instantiate it.
    if elementType == 'vpd_text':
        if request.method == 'POST':
            form = VPDTextForm(request.POST)
        else:
            form = VPDTextForm(initial=initialData)
    elif elementType == 'structured_patient_demographics':
        if request.method == 'POST':
            form = StructuredPatientDemographicsForm(request.POST)
        else:
            form = StructuredPatientDemographicsForm(initial=initialData)
    elif elementType == 'unstructured_patient_demographics':
        if request.method == 'POST':
            form = UnstructuredPatientDemographicsForm(request.POST)
        else:
            form = UnstructuredPatientDemographicsForm(initial=initialData)
    elif elementType == 'author_diagnoses':
        if request.method == 'POST':
            form = AuthorDiagnosesForm(request.POST)
        else:
            form = AuthorDiagnosesForm(initial=initialData)
    elif elementType == 'medication':
        if request.method == 'POST':
            form = MedicationForm(request.POST)
        else:
            form = MedicationForm(initial=initialData)
    elif elementType == 'interview_item':
        if request.method == 'POST':
            form = InterviewItemForm(request.POST)
        else:
            form = InterviewItemForm(initial=initialData)
    elif elementType == 'physical_exam':
        if request.method == 'POST':
            form = PhysicalExamForm(request.POST)
        else:
            form = PhysicalExamForm(initial=initialData)
    elif elementType == 'diagnostic_test':
        if request.method == 'POST':
            form = DiagnosticTestForm(request.POST)
        else:
            form = DiagnosticTestForm(initial=initialData)
    elif elementType == 'differential_test':
        if request.method == 'POST':
            form = DifferentialTestForm(request.POST)
        else:
            form = DifferentialTestForm(initial=initialData)
    elif elementType == 'differential_diagnosis':
        if request.method == 'POST':
            form = DifferentialDiagnosisForm(request.POST)
        else:
            form = DifferentialDiagnosisForm(initial=initialData)
    elif elementType == 'intervention':
        if request.method == 'POST':
            form = InterventionForm(request.POST)
        else:
            form = InterventionForm(initial=initialData)
    else:
        return HttpResponseRedirect(reverse('ol-readElements', kwargs={'labyrinthID': labyrinth.id}))
    
    # If the user is submitting an element for creation.
    if request.method == 'POST':        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newElement = form.save(commit=False)
            newElement.labyrinth = labyrinth
            newElement.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The element was created successfully.")
            
            # Re-direct to the element listing view.
            return HttpResponseRedirect(reverse('ol-readElements', kwargs={'labyrinthID': labyrinth.id}))
    
    return render_to_response('admin_element.html',
        {'form': form, 'labyrinth': labyrinth, 'createUrl': createUrl},
        context_instance=RequestContext(request))

##
## Read Elements (Administration)
## 
## This view is used to display the elements in a given labyrinth.
## 

@login_required
def admin_readElements(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')
    
    # Since the Element class is abstract, we can't use it directly
    # and we have to manually generate an array of its subclass instances.
    elements = []
    for element_subclass in Element.__subclasses__():
        elements.extend(list(element_subclass.objects.filter(labyrinth=labyrinthID)))

    # Generate the listing, using the generated list of elements
    # in this labyrinth and the custom template we've defined.
    return render_to_response('admin_object_list.html',
        {'labyrinth': Labyrinth.objects.get(id=labyrinthID), 'object_list': elements, 'object_name': 'Element'},
        context_instance=RequestContext(request))

## 
## Update Element (Administration)
## 
## This view is used to update an existing element.
## 

@login_required
def admin_updateElement(request, **arguments):
    
    # Get the element ID so that the generic view can retrieve its details.
    elementID = arguments.get('elementID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the element type from the URL
    elementType = arguments.get('elementType')
    
    # The user isn't specifying the element type in the URL.
    # Return them to the listing page with an error message.
    if not elementType:
        request.user.message_set.create(message="No element type was specified in the URL.")
        return HttpResponseRedirect(reverse('ol-readElements', kwargs={'labyrinthID': labyrinth.id}))
    
    # Figure out what kind of form we're working with and instantiate it.
    if elementType == 'VPDText':
        elementFormClass = VPDTextForm
    elif elementType == 'StructuredPatientDemographics':
        elementFormClass = StructuredPatientDemographicsForm
    elif elementType == 'UnstructuredPatientDemographics':
        elementFormClass = UnstructuredPatientDemographicsForm
    elif elementType == 'AuthorDiagnoses':
        elementFormClass = AuthorDiagnosesForm
    elif elementType == 'Medication':
        elementFormClass = MedicationForm
    elif elementType == 'InterviewItem':
        elementFormClass = InterviewItemForm
    elif elementType == 'PhysicalExam':
        elementFormClass = PhysicalExamForm
    elif elementType == 'DiagnosticTest':
        elementFormClass = DiagnosticTestForm
    elif elementType == 'DifferentialTest':
        elementFormClass = DifferentialTestForm
    elif elementType == 'DifferentialDiagnosis':
        elementFormClass = DifferentialDiagnosisForm
    elif elementType == 'Intervention':
        elementFormClass = InterventionForm
    else:
        # The user is trying to update an element type that we're not aware of.
        # Return them to the listing page with an error message.
        request.user.message_set.create(message="The element type specified in the URL does not exist.")
        return HttpResponseRedirect(reverse('ol-readElements', kwargs={'labyrinthID': labyrinth.id}))
    
    # Handle an update request.
    if request.POST:
        # Initialize the form using the request data and the original instance.
        form = elementFormClass(request.POST, instance=elementFormClass.Meta.model.objects.get(id=elementID))
        
        # Delete the VPD element type dropdown, since it's not actually saved.
        # This is because the user cannot change VPD element types for pre-existing elements.
        del form.fields['vpd_element_type']
        
        # If the form checks out, save it and re-direct to the listing page.
        if form.is_valid():
            new_element = form.save(commit=False)
            new_element.labyrinth = labyrinth
            new_element.save()
            return HttpResponseRedirect(reverse('ol-readElements', kwargs={'labyrinthID': labyrinth.id}))
    else:
        # Initialize the form using the original instance.
        form = elementFormClass(instance=elementFormClass.Meta.model.objects.get(id=elementID))
        
        # Delete the VPD element type dropdown, since it should not be displayed.
        # This is because the user cannot change VPD element types for pre-existing elements.
        del form.fields['vpd_element_type']
    
    # Generate the edit page.
    return render_to_response('admin_object.html',
        {'form': form, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

## 
## Delete Element (Administration)
## 
## This view is used to delete an existing element.
## 

@login_required
def admin_deleteElement(request, **arguments):
    
    # Get the element ID so that the generic view can retrieve its details.
    elementID = arguments.get('elementID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the element type from the URL
    elementType = arguments.get('elementType')
    
    # The user isn't specifying the element type in the URL.
    # Return them to the listing page with an error message.
    if not elementType:
        request.user.message_set.create(message="No element type was specified in the URL.")
        return HttpResponseRedirect(reverse('ol-readElements', kwargs={'labyrinthID': labyrinth.id}))
    
    # Figure out what kind of element we're working with.
    if elementType == 'VPDText':
        elementClass = VPDText
    elif elementType == 'StructuredPatientDemographics':
        elementClass = StructuredPatientDemographics
    elif elementType == 'UnstructuredPatientDemographics':
        elementClass = UnstructuredPatientDemographics
    elif elementType == 'AuthorDiagnoses':
        elementClass = AuthorDiagnoses
    elif elementType == 'Medication':
        elementClass = Medication
    elif elementType == 'InterviewItem':
        elementClass = InterviewItem
    elif elementType == 'PhysicalExam':
        elementClass = PhysicalExam
    elif elementType == 'DiagnosticTest':
        elementClass = DiagnosticTest
    elif elementType == 'DifferentialTest':
        elementClass = DifferentialTest
    elif elementType == 'DifferentialDiagnosis':
        elementClass = DifferentialDiagnosis
    elif elementType == 'Intervention':
        elementClass = Intervention
    else:
        # The user is trying to update an element type that we're not aware of.
        # Return them to the listing page with an error message.
        request.user.message_set.create(message="The element type specified in the URL does not exist.")
        return HttpResponseRedirect(reverse('ol-readElements', kwargs={'labyrinthID': labyrinth.id}))

    # Call on a generic view to delete the element.
    return delete_object(request,
        model=elementClass,
        object_id=elementID,
        login_required=True,
        post_delete_redirect=reverse('ol-readElements', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create Cluster (Administration)
## 
## This view is used to create a new cluster.
## 

@login_required
def admin_createCluster(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting an cluster for creation.
    if request.method == 'POST':
        form = ClusterForm(request.POST)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newCluster = form.save(commit=False)
            newCluster.labyrinth = labyrinth
            newCluster.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The cluster was created successfully.")
            
            # Re-direct to the cluster listing view.
            return HttpResponseRedirect(reverse('ol-readClusters', kwargs={'labyrinthID': labyrinth.id}))
    else:
        form = ClusterForm()
    
    return render_to_response('admin_object.html',
        {'form': form, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Clusters (Administration)
## 
## This view is used to display the clusters in a given labyrinth.
## 

@login_required
def admin_readClusters(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the clusters of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Cluster.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Cluster (Administration)
## 
## This view is used to update an existing cluster.
## 

@login_required
def admin_updateCluster(request, **arguments):
    
    # Get the cluster so that we can retrieve its details.
    cluster = Cluster.objects.get(id=arguments.get('clusterID'))
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the list of items belonging to this cluster
    cluster_items = ClusterItem.objects.filter(cluster=cluster.id).order_by('index')
    
    # Get the list of assets available to this cluster
    assets = Asset.objects.filter(labyrinth=labyrinth)
    
    # Get the list of clusters available to this cluster
    clusters = Cluster.objects.filter(labyrinth=labyrinth).exclude(id=cluster.id)
    
    # Since the Element class is abstract, we can't use it directly
    # and we have to manually generate an array of its subclass instances.
    elements = []
    for element_subclass in Element.__subclasses__():
        elements.extend(list(element_subclass.objects.filter(labyrinth=labyrinth.id)))
    
    if 'element' in request.POST:
        # Rip the element field apart to get its constituent parts
        element_info = request.POST.get('element').split(':')
        
        # Validate that this object exists
        model_classes = __import__('openlabyrinth.models')
        element_class = getattr(model_classes.models, element_info[0])
        if element_class.objects.get(id=element_info[1]):
            # The object exists! We can safely add it to the cluster now.
            new_cluster_item = ClusterItem()
            new_cluster_item.cluster = cluster
            new_cluster_item.index = ClusterItem.objects.filter(cluster=cluster.id).count()
            new_cluster_item.trigger = request.POST.get('element_trigger')
            new_cluster_item.class_name = element_info[0]
            new_cluster_item.object_id = element_info[1]
            try:
                # Validate the new object.
                new_cluster_item.full_clean()
                new_cluster_item.save()
            except ValidationError, e:
                # The element specified by the user doesn't exist.
                request.user.message_set.create(message="An error occurred when trying to add the element to this cluster.")
                
        else:
            # The element specified by the user doesn't exist.
            request.user.message_set.create(message="The element you tried to add doesn't exist.")
            

        # Re-direct to the update page. This prevents the default form from being submitted.
        return HttpResponseRedirect(reverse('ol-updateCluster', kwargs={'labyrinthID': labyrinth.id, 'clusterID': cluster.id}))
        
    elif 'asset' in request.POST:        
        if Asset.objects.get(id=request.POST['asset']):
            new_cluster_item = ClusterItem()
            new_cluster_item.cluster = cluster
            new_cluster_item.index = ClusterItem.objects.filter(cluster=cluster.id).count()
            new_cluster_item.class_name = 'Asset'
            new_cluster_item.object_id = request.POST['asset']
            try:
                # Validate the new object.
                new_cluster_item.full_clean()
                new_cluster_item.save()
            except ValidationError, e:
                # The element specified by the user doesn't exist.
                request.user.message_set.create(message="An error occurred when trying to add the asset to this cluster.")
        else:
            # The asset specified by the user doesn't exist.
            request.user.message_set.create(message="The asset you tried to add doesn't exist.")

        # Re-direct to the update page. This prevents the default form from being submitted.
        return HttpResponseRedirect(reverse('ol-updateCluster', kwargs={'labyrinthID': labyrinth.id, 'clusterID': cluster.id}))
        
    elif 'cluster' in request.POST:        
        if Cluster.objects.get(id=request.POST['cluster']):
            new_cluster_item = ClusterItem()
            new_cluster_item.cluster = cluster
            new_cluster_item.index = ClusterItem.objects.filter(cluster=cluster.id).count()
            new_cluster_item.class_name = 'Cluster'
            new_cluster_item.object_id = request.POST['cluster']
            try:
                # Validate the new object.
                new_cluster_item.full_clean()
                new_cluster_item.save()
            except ValidationError, e:
                # The element specified by the user doesn't exist.
                request.user.message_set.create(message="An error occurred when trying to add the cluster to this cluster.")
        else:
            # The asset specified by the user doesn't exist.
            request.user.message_set.create(message="The cluster you tried to add doesn't exist.")

        # Re-direct to the update page. This prevents the default form from being submitted.
        return HttpResponseRedirect(reverse('ol-updateCluster', kwargs={'labyrinthID': labyrinth.id, 'clusterID': cluster.id}))
    
    elif 'items' in request.POST:
        # Save the order of cluster items
        if request.POST.get('items').lstrip() != '':
            for index, item in enumerate(request.POST.get('items').split(',')):
                cluster_item = ClusterItem.objects.get(id=item)
                if cluster_item:
                    cluster_item.index = index
                    cluster_item.save()
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the
    # cluster data and the custom template we've defined.
    return update_object(request,
        form_class=ClusterForm,
        object_id=cluster.id,
        login_required=True,
        post_save_redirect=reverse('ol-readClusters', kwargs={'labyrinthID':labyrinth.id}),
        extra_context={'labyrinth': labyrinth, 'assets': assets, 'elements': elements, 'clusters': clusters, 'cluster_items': cluster_items, 'trigger_types':CLUSTER_ITEM_TRIGGER_TYPES},
        template_name='admin_cluster.html')

## 
## Delete Cluster (Administration)
## 
## This view is used to delete an existing cluster.
## 

@login_required
def admin_deleteCluster(request, **arguments):
    
    # Get the cluster ID so that the generic view can retrieve its details.
    clusterID = arguments.get('clusterID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Delete all of the associated cluster items.
    ClusterItem.objects.filter(cluster__id=clusterID).delete()

    # Call on a generic view to delete the cluster,
    # using the custom template we've defined.
    return delete_object(request,
        model=Cluster,
        object_id=clusterID,
        login_required=True,
        post_delete_redirect=reverse('ol-readClusters', kwargs={'labyrinthID':labyrinth.id}))

## 
## Delete Cluster Item (Administration)
## 
## This view is used to delete an existing cluster item.
## 

@login_required
def admin_deleteClusterItem(request, **arguments):
    
    # Get the cluster item ID so that the generic view can retrieve its details.
    clusterItem = ClusterItem.objects.get(id=arguments.get('clusterItemID'))
    
    # Get the cluster ID so that we can redirect to the update page.
    clusterID = arguments.get('clusterID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    if clusterItem:
        # Store the value so we can still access it after its containing object is deleted.
        index = clusterItem.index
            
        # Delete the cluster item and decrement any items
        # with higher indices to fill in the gap left behind.
        clusterItem.delete()
        cluster_items = ClusterItem.objects.filter(index__gt=index).order_by('index')
        for item in cluster_items:
            item.index -= 1
            item.save()
    else:
        # The element specified by the user doesn't exist.
        request.user.message_set.create(message="An error occurred when trying to add the cluster to this cluster.")

    # Re-direct to the update page.
    return HttpResponseRedirect(reverse('ol-updateCluster', kwargs={'labyrinthID': labyrinth.id, 'clusterID': clusterID}))

## 
## Create Avatar (Administration)
## 
## This view is used to create a new avatar.
## 

@login_required
def admin_createAvatar(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting an avatar for creation.
    if request.method == 'POST':
        form = AvatarForm(request.POST)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newAvatar = form.save(commit=False)
            newAvatar.labyrinth = labyrinth
            newAvatar.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The avatar was created successfully.")
            
            # Re-direct to the node listing view.
            return HttpResponseRedirect(reverse('ol-readAvatars', kwargs={'labyrinthID': labyrinth.id}))
    else:
        form = AvatarForm()
    
    return render_to_response('admin_avatar.html',
        {'form': form, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Avatars (Administration)
## 
## This view is used to display the avatars in a given labyrinth.
## 

@login_required
def admin_readAvatars(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the avatars of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Avatar.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Avatar (Administration)
## 
## This view is used to update an existing avatar.
## 

@login_required
def admin_updateAvatar(request, **arguments):
    
    # Get the avatar ID so that the generic view can retrieve its details.
    avatarID = arguments.get('avatarID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the avatar data
    # and the custom template we've defined.
    return update_object(request,
        form_class=AvatarForm,
        object_id=avatarID,
        login_required=True,
        post_save_redirect=reverse('ol-readAvatars', kwargs={'labyrinthID':labyrinth.id}),
        extra_context={'labyrinth': labyrinth},
        template_name='admin_avatar.html')

## 
## Delete Avatar (Administration)
## 
## This view is used to delete an existing avatar.
## 

@login_required
def admin_deleteAvatar(request, **arguments):
    
    # Get the avatar ID so that the generic view can retrieve its details.
    avatarID = arguments.get('avatarID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is trying to delete an avatar, we need to remove any
    # references made to it by nodes.
    if request.method == 'POST':
        for node in Node.objects.filter(avatar=Avatar.objects.get(id=avatarID)):
            node.avatar = None
            node.save()

    # Call on a generic view to delete the avatar,
    # using the custom template we've defined.
    return delete_object(request,
        model=Avatar,
        object_id=avatarID,
        login_required=True,
        post_delete_redirect=reverse('ol-readAvatars', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create Remote State Observer (Administration)
## 
## This view is used to create a new remote state observer.
## 

@login_required
def admin_createRemoteStateObserver(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting a remote state observer for creation.
    if request.method == 'POST':
        form = RemoteStateObserverForm(request.POST)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newRemoteStateObserver = form.save(commit=False)
            logging.debug(request.POST)
            logging.debug(newRemoteStateObserver.enabled)
            newRemoteStateObserver.labyrinth = labyrinth
            newRemoteStateObserver.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The remote state observer was created successfully.")
            
            # Re-direct to the remote state observer listing view.
            return HttpResponseRedirect(reverse('ol-readRemoteStateObservers', kwargs={'labyrinthID': labyrinth.id}))
    else:
        form = RemoteStateObserverForm()
    
    return render_to_response('admin_object.html',
        {'form': form, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Remote State Observers (Administration)
## 
## This view is used to display the remote state observers in a given labyrinth.
## 

@login_required
def admin_readRemoteStateObservers(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the remote state
    # observers of this labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=RemoteStateObserver.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID), 'object_name': 'Remote State Observer'})

## 
## Update Remote State Observer (Administration)
## 
## This view is used to update an existing remote state observer.
## 

@login_required
def admin_updateRemoteStateObserver(request, **arguments):
    
    # Get the remote state observer ID so that the generic view can retrieve its details.
    remoteStateObserverID = arguments.get('remoteStateObserverID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the remote
    # state observer data and the custom template we've defined.
    return update_object(request,
        form_class=RemoteStateObserverForm,
        object_id=remoteStateObserverID,
        login_required=True,
        post_save_redirect=reverse('ol-readRemoteStateObservers', kwargs={'labyrinthID':labyrinth.id}),
        extra_context={'labyrinth': labyrinth},
        template_name='admin_object.html')

## 
## Delete Remote State Observer (Administration)
## 
## This view is used to delete an existing remote state observer.
## 

@login_required
def admin_deleteRemoteStateObserver(request, **arguments):
    
    # Get the remote state observer ID so that the generic view can retrieve its details.
    remoteStateObserverID = arguments.get('remoteStateObserverID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # Call on a generic view to delete the remote state
    # observer, using the custom template we've defined.
    return delete_object(request,
        model=RemoteStateObserver,
        object_id=remoteStateObserverID,
        login_required=True,
        post_delete_redirect=reverse('ol-readRemoteStateObservers', kwargs={'labyrinthID':labyrinth.id}))

## 
## Export Labyrinth (Administration)
## 
## This view is used to export a labyrinth.
## 

@login_required
def admin_exportLabyrinth(request, **arguments):
    
    # Get the labyrinth object that we're working with.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # The user has submitted a request to export the labyrinth
    if request.POST:
        
        # Get the data required to export the labyrinth
        nodes = Node.objects.filter(labyrinth=labyrinth)
        links = Link.objects.filter(labyrinth=labyrinth)
        counters = Counter.objects.filter(labyrinth=labyrinth)
        assets = Asset.objects.filter(labyrinth=labyrinth)
        avatars = Avatar.objects.filter(labyrinth=labyrinth)
        
        # Initialize a zip archive and add all of the static files.
        temp = tempfile.TemporaryFile()
        archive = zipfile.ZipFile(temp, 'w', zipfile.ZIP_DEFLATED)
        os.chdir('/home/jmacdonald/django/devbox/openlabyrinth/mvp')
        recursive_zip(archive)
        
        # Generate and save the virtual patient data
        template = loader.get_template('mvp/virtualpatientdata.xml')
        context = Context({
            'avatars' : avatars
            })
        
        rendered_template = template.render(context)
        archive.writestr('virtualpatientdata.xml', rendered_template)
        
        # Generate and save the activity model
        template = loader.get_template('mvp/activitymodel.xml')
        context = Context({
            'nodes': nodes,
            'links' : links,
            'counters' : counters
            })
        
        rendered_template = template.render(context)
        archive.writestr('activitymodel.xml', rendered_template)
        
        # Generate and save the imsmanifest file
        template = loader.get_template('mvp/imsmanifest.xml')
        context = Context({
            'assets' : assets
            })
        
        rendered_template = template.render(context)
        archive.writestr('imsmanifest.xml', rendered_template)
        
        # Generate and save the metadata file
        template = loader.get_template('mvp/metadata.xml')
        context = Context({
            'labyrinth' : labyrinth,
            'licence' : request.POST.get('licence')
            })
        
        rendered_template = template.render(context)
        archive.writestr('metadata.xml', rendered_template)
        
        # Generate and save the data availability model
        template = loader.get_template('mvp/dataavailabilitymodel.xml')
        context = Context({
            'labyrinth' : labyrinth,
            'licence' : request.POST.get('licence')
            })
        
        rendered_template = template.render(context)
        archive.writestr('dataavailabilitymodel.xml', rendered_template)
        
        archive.close()
        wrapper = FileWrapper(temp)
        response = HttpResponse(wrapper, content_type='application/zip')
        response['Content-Disposition'] = 'attachment; filename=test.zip'
        response['Content-Length'] = temp.tell()
        temp.seek(0)
        return response
        
    else:
    
        return render_to_response('admin_labyrinth_export.html',
            {'form': LabyrinthExportForm(), 'labyrinth': labyrinth},
            context_instance=RequestContext(request))
        
## 
## Create Labyrinth (Administration)
## 
## This view is used to create a new labyrinth.
## 

@login_required
def admin_createLabyrinth(request, **arguments):
    
    # If the user is submitting a labyrinth for creation.
    if request.method == 'POST':
        form = LabyrinthForm(request.POST)
        
        # If the form is valid, save it.
        if form.is_valid():
            form.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The labyrinth was created successfully.")
            
            # Re-direct to the node listing view.
            return HttpResponseRedirect(reverse('ol-readLabyrinths'))
    else:
        form = LabyrinthForm()
    
    return render_to_response('admin_object.html',
        {'form': form},
        context_instance=RequestContext(request))

##
## Read Labyrinths (Administration)
## 
## This view is used to display the list of available labyrinths.
## 

@login_required
def admin_readLabyrinths(request, **arguments):

    # Call on a generic view to generate the listing, using the nodes of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Labyrinth.objects.all(),
        template_name='admin_object_list.html')

##
## Read Labyrinth (Administration)
## 
## This view is used to display information about a labyrinth.
## 

@login_required
def admin_readLabyrinth(request, **arguments):
    
    # Get the labyrinth object that we're working with.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # Call on a generic view to generate the listing, using the nodes of this
    # labyrinth and the custom template we've defined.
    return render_to_response('admin_labyrinth.html',
        {'labyrinth': labyrinth},
        context_instance=RequestContext(request))

## 
## Update Labyrinth (Administration)
## 
## This view is used to update an existing labyrinth.
## 

@login_required
def admin_updateLabyrinth(request, **arguments):
    
    # Get the labyrinth ID so that the generic view can retrieve its details.
    labyrinthID = arguments.get('labyrinthID')
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the node data
    # and the custom template we've defined. An extra context is used
    # to include the JavaScript library for TinyMCE initialization.
    return update_object(request,
        form_class=LabyrinthForm,
        object_id=labyrinthID,
        login_required=True,
        post_save_redirect=reverse('ol-readLabyrinth', kwargs={'labyrinthID': labyrinthID}),
        extra_context={'labyrinth': Labyrinth.objects.get(id=arguments.get('labyrinthID'))},
        template_name='admin_object.html'
        )

## 
## Delete Labyrinth (Administration)
## 
## This view is used to delete an existing labyrinth.
## 

@login_required
def admin_deleteLabyrinth(request, **arguments):
    
    # Get the labyrinth object that we're working with.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Delete all sub-objects (those indirectly linked
    # to a labyrinth through one of its objects) first.
    for node in Node.objects.filter(labyrinth=labyrinth):
        NodeVariance.objects.filter(node=node).delete()
        NodeCondition.objects.filter(node=node).delete()
    for link in Link.objects.filter(labyrinth=labyrinth):
        LinkVariance.objects.filter(link=link).delete()
    for counter in Counter.objects.filter(labyrinth=labyrinth):
        Alert.objects.filter(counter=counter).delete()
    for report in Report.objects.filter(labyrinth=labyrinth):
        # Get all of the node records associated with this report.
        nodeRecords = NodeRecord.objects.filter(report=report)
        
        # Delete any counter records associated with the node records.
        for nodeRecord in nodeRecords:
            CounterRecord.objects.filter(node_record=nodeRecord).delete()
        
        # Delete the node records.
        nodeRecords.delete()
    
    # Delete all objects that are contained within this labyrinth
    # before deleting the labyrinth itself. The order of deletion
    # is important, as some objects reference one another.
    Link.objects.filter(labyrinth=labyrinth).delete()
    Feedback.objects.filter(labyrinth=labyrinth).delete()
    Node.objects.filter(labyrinth=labyrinth).delete()
    Counter.objects.filter(labyrinth=labyrinth).delete()
    Asset.objects.filter(labyrinth=labyrinth).delete()
    Avatar.objects.filter(labyrinth=labyrinth).delete()
    RemoteStateObserver.objects.filter(labyrinth=labyrinth).delete()
    Section.objects.filter(labyrinth=labyrinth).delete()
    Question.objects.filter(labyrinth=labyrinth).delete()
    Report.objects.filter(labyrinth=labyrinth).delete()
    
    # Call on a generic view to delete the labyrinth.
    return delete_object(request,
        model=Labyrinth,
        object_id=labyrinth.id,
        login_required=True,
        post_delete_redirect=reverse('ol-readLabyrinths'))

## 
## Create Asset (Administration)
## 
## This view is used to create a new asset.
## 

@login_required
def admin_createAsset(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # If the user is submitting an asset for creation.
    if request.method == 'POST':
        form = AssetForm(request.POST, request.FILES)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in .
        if form.is_valid():
            newAsset = form.save(commit=False)
            newAsset.labyrinth = labyrinth
            newAsset.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The asset was created successfully.")
            
            # Re-direct to the asset listing view.
            return HttpResponseRedirect(reverse('ol-readAssets', kwargs={'labyrinthID':labyrinth.id}))
    else:
        form = AssetForm()

    return render_to_response('admin_object.html',
        {'form': form, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Assets (Administration)
## 
## This view is used to display the assets in a given labyrinth.
## 

@login_required
def admin_readAssets(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the assets of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Asset.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Asset (Administration)
## 
## This view is used to update an existing asset.
## 

@login_required
def admin_updateAsset(request, **arguments):
    
    # Get the asset ID so that the generic view can retrieve its details.
    assetID = arguments.get('assetID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the node data
    # and the custom template we've defined. An extra context is used
    # to include the JavaScript library for TinyMCE initialization.
    return update_object(request,
        form_class=AssetForm,
        object_id=assetID,
        login_required=True,
        post_save_redirect=reverse('ol-readAssets', kwargs={'labyrinthID':labyrinth.id}),
        extra_context={'labyrinth': labyrinth},
        template_name='admin_object.html')

## 
## Delete Asset (Administration)
## 
## This view is used to delete an existing asset.
## 

@login_required
def admin_deleteAsset(request, **arguments):
    
    # Get the asset ID so that the generic view can retrieve its details.
    assetID = arguments.get('assetID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # Call on a generic view to generate the listing, using the
    # assets of this labyrinth and the custom template we've defined.
    return delete_object(request,
        model=Asset,
        object_id=assetID,
        login_required=True,
        post_delete_redirect=reverse('ol-readAssets', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create Section (Administration)
## 
## This view is used to create a new section.
## 

@login_required
def admin_createSection(request, **arguments):
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the list of available (unassigned) nodes.
    availableNodes = Node.objects.filter(section=None, labyrinth=labyrinth)
    
    # If the user is submitting an section for creation.
    if request.method == 'POST':
        form = SectionForm(request.POST)
            
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in.
        if form.is_valid():
            newSection = form.save(commit=False)
            newSection.labyrinth = labyrinth
            newSection.save()
        
            sectionNodeIDs = request.POST.get('nodes').split(',')
            nodeIndex = 0
            if len(sectionNodeIDs) > 0:
                for nodeID in sectionNodeIDs:
                    targetNode = Node.objects.get(id=nodeID)
                    targetNode.section = newSection
                    targetNode.section_index = nodeIndex
                    targetNode.save()
                    nodeIndex+=1
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The section was created successfully.")
            
            # Re-direct to the node listing view.
            return HttpResponseRedirect(reverse('ol-readSections', kwargs={'labyrinthID': labyrinth.id}))
    else:
        form = SectionForm()
    
    return render_to_response('admin_section.html',
        {'form': form, 'availableNodes': availableNodes, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

##
## Read Sections (Administration)
## 
## This view is used to display the sections in a given labyrinth.
## 

@login_required
def admin_readSections(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the sections of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Section.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID)})

## 
## Update Section (Administration)
## 
## This view is used to update an existing section.
## 

@login_required
def admin_updateSection(request, **arguments):
    
    # Get the section so that we can update its details.
    section = Section.objects.get(id=arguments.get('sectionID'))
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get the list of available (unassigned) nodes.
    availableNodes = Node.objects.filter(section=None, labyrinth=labyrinth)
    
    # Get the list of section (assigned) nodes.
    sectionNodes = Node.objects.filter(section=section).order_by('section_index')
    
    # If the user is submitting an section for update.
    if request.method == 'POST':
        form = SectionForm(request.POST, instance=section)
        
        # If the form is valid, store the data, manually 
        # adding a reference to the labyrinth that we're working in.
        if form.is_valid():
            newSection = form.save(commit=False)
            newSection.labyrinth = labyrinth
            newSection.save()
            
            # Clear all of the previous records.
            for node in sectionNodes:
                node.section = None
                node.section_index = None
                node.save()
        
            sectionNodeIDs = request.POST.get('nodes').split(',')
            nodeIndex = 0
            if len(sectionNodeIDs) > 0:
                for nodeID in sectionNodeIDs:
                    if not nodeID == '':
                        targetNode = Node.objects.get(id=nodeID)
                        targetNode.section = newSection
                        targetNode.section_index = nodeIndex
                        targetNode.save()
                        nodeIndex+=1
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The section was updated successfully.")
            
            # Re-direct to the node listing view.
            return HttpResponseRedirect(reverse('ol-readSections', kwargs={'labyrinthID': labyrinth.id}))
    else:
        form = SectionForm(instance=section)
    
    return render_to_response('admin_section.html',
        {'form': form, 'availableNodes': availableNodes, 'sectionNodes': sectionNodes, 'labyrinth': labyrinth},
        context_instance=RequestContext(request))

## 
## Delete Section (Administration)
## 
## This view is used to delete an existing section.
## 

@login_required
def admin_deleteSection(request, **arguments):
    
    # Get the section so that the generic view can retrieve its details.
    section = Section.objects.get(id=arguments.get('sectionID'))
            
    # Remove all of the references to this section before deleting it.
    sectionNodes = Node.objects.filter(section=section)
    for node in sectionNodes:
        node.section = None
        node.section_index = None
        node.save()
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))

    # Call on a generic view to delete the section,
    # using the custom template we've defined.
    return delete_object(request,
        model=Section,
        object_id=section.id,
        login_required=True,
        post_delete_redirect=reverse('ol-readSections', kwargs={'labyrinthID':labyrinth.id}))

## 
## Create User (Administration)
## 
## This view is used to create a new user.
## 

@login_required
def admin_createUser(request, **arguments):
    
    # If the user is submitting a user for creation.
    if request.method == 'POST':
        form = UserForm(request.POST)
        
        # If the form is valid, save it.
        if form.is_valid():
            form.save()
                
            # Let the user know that the operation was successful.
            request.user.message_set.create(message="The user was created successfully.")
            
            # Re-direct to the user listing view.
            return HttpResponseRedirect(reverse('ol-readUsers'))
    else:
        form = UserForm()
    
    return render_to_response('admin_object.html',
        {'form': form},
        context_instance=RequestContext(request))

##
## Read Users (Administration)
## 
## This view is used to display the assets in a given labyrinth.
## 

@login_required
def admin_readUsers(request, **arguments):
    
    # Call on a generic view to generate the listing, using the users
    # of this labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=User.objects.all(),
        template_name='admin_object_list.html')

## 
## Update User (Administration)
## 
## This view is used to update an existing user.
## 

@login_required
def admin_updateUser(request, **arguments):
    
    # Get the user ID so that the generic view can retrieve its details.
    userID = arguments.get('userID')
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the custom
    # template we've defined. An extra context is used to let the
    # template know that we're updating an object and not creating one.
    return update_object(request,
        form_class=UserForm,
        object_id=userID,
        login_required=True,
        post_save_redirect=reverse('ol-readUsers'),
        template_name='admin_object.html')

## 
## Delete User (Administration)
## 
## This view is used to delete an existing user.
## 

@login_required
def admin_deleteUser(request, **arguments):
    
    # Get the user ID so that the generic view can retrieve its details.
    userID = arguments.get('userID')

    # Call on a generic view to generate the listing, using the nodes of this
    # labyrinth and the custom template we've defined.
    return delete_object(request,
        model=User,
        object_id=userID,
        login_required=True,
        post_delete_redirect=reverse('ol-readUsers'))

## 
## Create Skin (Administration)
## 
## This view is used to create a new skin.
## 

@login_required
def admin_createSkin(request, **arguments):
    
    return create_object(request,
        model=Skin,
        login_required=True,
        post_save_redirect=reverse('ol-readSkins'),
        template_name='admin_object.html')

##
## Read Skins (Administration)
## 
## This view is used to display the list of skins.
## 

@login_required
def admin_readSkins(request, **arguments):
    
    # Call on a generic view to generate the listing, using the users
    # of this labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Skin.objects.all(),
        template_name='admin_object_list.html')

## 
## Update Skin (Administration)
## 
## This view is used to update an existing skin.
## 

@login_required
def admin_updateSkin(request, **arguments):
    
    # Get the user ID so that the generic view can retrieve its details.
    skinID = arguments.get('skinID')
    
    # Call on a generic view to handle generation of the edit page,
    # as well as saving the data and re-directing using the custom
    # template we've defined. An extra context is used to let the
    # template know that we're updating an object and not creating one.
    return update_object(request,
        model=Skin,
        object_id=skinID,
        login_required=True,
        post_save_redirect=reverse('ol-readSkins'),
        template_name='admin_object.html')

## 
## Delete Skin (Administration)
## 
## This view is used to delete an existing skin.
## 

@login_required
def admin_deleteSkin(request, **arguments):
    
    # Get the user ID so that the generic view can retrieve its details.
    skinID = arguments.get('skinID')

    # Call on a generic view to generate the listing, using the nodes of this
    # labyrinth and the custom template we've defined.
    return delete_object(request,
        model=Skin,
        object_id=skinID,
        login_required=True,
        post_delete_redirect=reverse('ol-readSkins'))

##
## Read Reports (Administration)
## 
## This view is used to display the reports in a given labyrinth.
## 

@login_required
def admin_readReports(request, **arguments):
    
    # Get the ID of the labyrinth that we're working in.
    labyrinthID = arguments.get('labyrinthID')

    # Call on a generic view to generate the listing, using the reports of this
    # labyrinth and the custom template we've defined.
    return object_list(request,
        queryset=Report.objects.filter(labyrinth=labyrinthID),
        template_name='admin_object_list.html',
        extra_context={'labyrinth': Labyrinth.objects.get(id=labyrinthID), 'immutable': True})

##
## Read Report (Administration)
## 
## This view is used to display an existing report.
## 

@login_required
def admin_readReport(request, **arguments):
    
    # Get the report so that the generic view can retrieve its details.
    report = Report.objects.get(id=arguments.get('reportID'))
    
    # Get the labyrinth that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get all of the node records associated with this report.
    nodeRecords = NodeRecord.objects.filter(report=report)
    
    # Figure out the length of the session.
    sessionDuration = nodeRecords[len(nodeRecords)-1].timestamp - nodeRecords[0].timestamp
    sessionDuration = "%ih %im %is" % (sessionDuration.seconds/3600, (sessionDuration.seconds%3600)/60, sessionDuration.seconds%60)
    
    return object_detail(request,
        queryset=Report.objects.all(),
        object_id=report.id,
        template_name='admin_report.html',
        extra_context={
            'timeDataUrl': reverse('ol-getTimeReport', kwargs={'reportID': report.id}),
            'counterDataUrl': reverse('ol-getCounterReport', kwargs={'reportID': report.id}),
            'labyrinth': labyrinth,
            'username': report.user,
            'sessionID': report.session_id,
            'sessionStart': report.start_time,
            'sessionDuration': sessionDuration,
            'totalNodesVisited': len(report.noderecord_set.all()),
            'mustAvoidNodesVisited': len(report.noderecord_set.filter(node__type='avoid')),
            'mustVisitNodesVisited': len(report.noderecord_set.filter(node__type='visit'))
        })

## 
## Delete Report (Administration)
## 
## This view is used to delete an existing report.
## 

@login_required
def admin_deleteReport(request, **arguments):
    
    # Get the report ID so that the generic view can retrieve its details.
    reportID = arguments.get('reportID')
    
    # Get the labyrinth object that we're working in.
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    # Get all of the node records associated with this report.
    nodeRecords = NodeRecord.objects.filter(report=reportID)
    
    # Delete any counter records associated with the node records.
    for nodeRecord in nodeRecords:
        CounterRecord.objects.filter(node_record=nodeRecord).delete()
    
    # Delete the node records.
    nodeRecords.delete()
    
    return delete_object(request,
        model=Report,
        object_id=reportID,
        login_required=True,
        post_delete_redirect=reverse('ol-readReports', kwargs={'labyrinthID':labyrinth.id}))

## =============================================================================
## Viewer Views
## 
##   These views are responsible for sending a HTML/JavaScript based UIs that 
##   use AJAX to interact with the server; these employ the "XML Generator 
##   Views" to satisfy subsequent data requests.
## =============================================================================##

## Home
## 
## This view is used to generate XML that feeds the time report chart.
## 

@login_required
def home(request, **arguments):
    
    return render_to_response('home.html', {
        'logoutUrl': reverse('ol-logoutUser'),
        'labyrinthUrl': reverse('ol-readLabyrinths'),
        'siteRoot': reverse('ol-root')},
        context_instance=RequestContext(request))

## Get Time Report
## 
## This view is used to generate XML that feeds the time report chart.
## 

@login_required
def getTimeReport(request, **arguments):
    
    # Get the report so that the generic view can retrieve its details.
    report = Report.objects.get(id=arguments.get('reportID'))
    
    # Get the node data that relates to this report.
    nodeRecords = report.noderecord_set.all()
    
    nodeTimeColumns = []
    
    for index, nodeRecord in enumerate(nodeRecords):
        if index < len(nodeRecords)-1:
            if nodeRecord.node.type == 'avoid':
                nodeColor = 'FF0000'
            elif nodeRecord.node.type == 'visit':
                nodeColor = '00FF00'
            else:
                nodeColor = '0000FF'
            nodeTimeColumns.append({
                'nodeTitle': nodeRecord.node.title + " (%i)" % nodeRecord.node.id,
                'time': (nodeRecords[index+1].timestamp - nodeRecord.timestamp).seconds,
                'color': nodeColor
            })
    
    # Load the template and pass the data along
    t = loader.get_template('report_time.xml')
    c = RequestContext(request, {
        'nodeTimeColumns': nodeTimeColumns
    })
    return HttpResponse(t.render(c))
    
## Get Counter Report
## 
## This view is used to generate XML that feeds the time report chart.
## 

@login_required
def getCounterReport(request, **arguments):
    
    # Get the report so that the generic view can retrieve its details.
    report = Report.objects.get(id=arguments.get('reportID'))
    
    # Get the nodes that relate to this report.
    nodes = []
    for nodeRecord in report.noderecord_set.all():
        nodes.append(nodeRecord.node)
    
    counterColumns = []
    
    for counter in report.labyrinth.counter_set.all():
        counterColumns.append({'name': counter.name, 'records': CounterRecord.objects.filter(counter=counter)})
    
    # Load the template and pass the data along
    t = loader.get_template('report_counters.xml')
    c = RequestContext(request, {
        'counterColumns': counterColumns,
        'nodes': nodes
    })
    return HttpResponse(t.render(c))

## 
## Root view
##
## This view is the labyrinth listing seen when visiting the site root.
## 

@initialize
def getRoot(request, **arguments):
    
    # Get all of the labyrinths whose access is open.
    openLabyrinths = Labyrinth.objects.filter(access='open')
    
    # Remove any labyrinths that haven't defined a root node.
    openLabyrinths = openLabyrinths.exclude(root_node=None)
    
    t = loader.get_template('index.html')
    c = RequestContext(request, {
        'form' : loginForm,
        'openLabyrinths' : openLabyrinths,
        'siteRoot': reverse('ol-root')
    })
    return HttpResponse(t.render(c))

## 
## Get Node Viewer
## 
## This view is responsible for sending the JavaScript-based viewer.
## It is the only view that accepts a sessionID via the URL, so that it
## can bind said session to the viewer, which creates a cookie in the
## browser to simulate an authentic session.
## 

@initialize
def getNodeViewer(request, **arguments):
    
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
        
    # Check to see if a session ID has been passed as part of the URL.
    sessionID = arguments.get('sessionID')
    
    if sessionID != None:
        request.session = Session.objects.get(pk=sessionID)
    
    # Load the template and pass the data along
    t = loader.get_template('%s/skin.html' % labyrinth.skin.name)
    c = RequestContext(request, {
        'labyrinth': labyrinth,
        'sessionID': sessionID,
        'form' : loginForm
    })
    return HttpResponse(t.render(c))

## =============================================================================
## XML Views
## 
##   These views are responsible for generating labyrinth/node XML data.
##   Unlike the administrative views, these views are session-contextual and
##   their data depends not only on which object is requested, but which
##   user is requesting the object. These are the main functions used for
##   in-session interaction with OpenLabyrinth.
## =============================================================================

## 
## Get Labyrinth
##
## This view provides all of the details for a specific labyrinth.
## 

@initialize
def getLabyrinth(request, **arguments):

    labyrinthID = arguments.get('labyrinthID')
    
    # Filter the visible labyrinths based on the user's access level
    # TODO: Check whether user has access to this data!
    labyrinth = Labyrinth.objects.get(id=labyrinthID)

    xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n'
    t = loader.get_template('labyrinth.xml')
    c = RequestContext(request, {
        'labyrinth': labyrinth,
    })
    return HttpResponse(xmlHeader+t.render(c), mimetype="text/xml")

## 
## Get Labyrinths
##
## This view provides a listing of all of the labyrinths available to the user.
## 

@initialize
def getLabyrinths(request, **arguments):
    
    # Filter the visible labyrinths based on the user's access level
    if not request.user.is_authenticated():
        labyrinths = Labyrinth.objects.filter(access='open')
    else:
        labyrinths = Labyrinth.objects.all()
    
    # Remove any labyrinths that haven't defined a root node.
    labyrinths = labyrinths.exclude(root_node=None)
    
    t = loader.get_template('labyrinths.xml')
    c = RequestContext(request, {
        'labyrinths': labyrinths,
    })
    return HttpResponse(t.render(c), mimetype="text/xml")

## 
## Get Node
##
## This view provides the node data following a link traversal. It is the main
## function used for labyrinth interaction and is responsible for session setup,
## counter operations, node traversal tracking, RSO notification, alerts, link
## evaluation, media resource inclusion, as well as reporting routines.
## 

@initialize
def getNode(request, **arguments):
    
    labyrinth = Labyrinth.objects.get(id=arguments.get('labyrinthID'))
    
    if not arguments.get('root'):
        # Get the node being requested by the user.
        node = Node.objects.get(id=arguments.get('nodeID'))
        
        try:
            # Figure out what link was used to get to this node.
            previousNode = Node.objects.get(id=request.session['accessedNodes'][len(request.session['accessedNodes'])-1])
            link = Link.objects.get(source=previousNode, destination=node)
        # If the user is requesting a node without having requested the root
        # node first, re-direct them to the root of the labyrinth.
        except KeyError:
            arguments['root'] = True
            return getNode(request, **arguments)
    
        # Execute any counter operations that are bound to this link.
        variances = LinkVariance.objects.filter(link=link.id)
        for variance in variances:
            if(variance.operation == '='):
                request.session['counters'][variance.counter.id] = variance.operation_value
            elif(variance.operation == '+'):
                request.session['counters'][variance.counter.id] += variance.operation_value
            elif(variance.operation == '-'):
                request.session['counters'][variance.counter.id] -= variance.operation_value
    else:
        # User is starting a new labyrinth
        clearSession(request)
        
        # Save the id and set the state of the labyrinth
        request.session['currentLabyrinth'] = labyrinth.id
        request.session['pauseTime'] = 0
        
        # We need to figure out the node ID because it's not in the URL
        node = labyrinth.root_node
        
        # Set the labyrinth pause state
        request.session['paused'] = 0
        request.session['pauseMessage'] = ''
        
        # Establish the time limit
        timeLimit = labyrinth.time_limit
        
        if (timeLimit != None):
            request.session['timeLimit'] = timeLimit
            request.session['startTime'] = time.time()
            
            request.session['timeRemaining'] = timeLimit
        else:
            request.session['timeLimit'] = False
            request.session['timeRemaining'] = '--'
        
        # Create a new report for logging purposes
        if not request.user.is_authenticated():
            report = Report(session_id=request.session.session_key, 
                start_time=request.session.get('startTime'), labyrinth=labyrinth)
        else:
            report = Report(session_id=request.session.session_key, 
                start_time=request.session.get('startTime'), user=request.user, labyrinth=labyrinth)
        report.save()
        request.session['report'] = report
        
        # Start up any counters for this labyrinth
        request.session['counters'] = {}
        for counter in Counter.objects.filter(labyrinth=labyrinth):
            request.session['counters'][counter.id] = counter.start_value
        
    # Save the current node ID.
    request.session['currentNode'] = node.id
    
    # Execute any counter operations that are bound to this node
    variances = NodeVariance.objects.filter(node=node)
    for variance in variances:
        if(variance.operation == '='):
            request.session['counters'][variance.counter.id] = variance.operation_value
        elif(variance.operation == '+'):
            request.session['counters'][variance.counter.id] += variance.operation_value
        elif(variance.operation == '-'):
            request.session['counters'][variance.counter.id] -= variance.operation_value
    
    # Merge the current counter values with their actual data
    counterData = []
    for counter_id, counterValue in request.session['counters'].iteritems():
        tempCounter = {}
        tempCounterData = Counter.objects.get(id=counter_id)
        tempCounter['id'] = counter_id
        tempCounter['name'] = tempCounterData.name
        tempCounter['description'] = tempCounterData.description
        tempCounter['value'] = counterValue
        tempCounter['name'] = tempCounterData.name
        tempCounter['unit'] = tempCounterData.unit
        tempCounter['visible'] = tempCounterData.visible
        counterData.append(tempCounter)
    
    # Track the user's node traversal path by appending the
    # current node to an array of previously accessed nodes.
    if('accessedNodes' in request.session):
        request.session['accessedNodes'].append(node.id)
    else:
        request.session['accessedNodes'] = [node.id]
    
    # Try broadcasting these changes to any enabled remote state observers using
    # a socket. If it fails, send an error to the user interface.
    try:
        for rso in RemoteStateObserver.objects.filter(labyrinth=labyrinth, enabled=True):
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect((rso.hostname, rso.port))
            t = loader.get_template('session.xml')
            c = RequestContext(request, {
                'state': request.session,
                'counters': counterData
            })
            sock.send(t.render(c))
            response = sock.recv(1024)
            sock.close()
    except:
        t = loader.get_template('error.xml')
        c = RequestContext(request, {
            'title': 'Communication Error',
            'message': '''One of the remote state observers registered
                to this labyrinth cannot be reached.''',
        })
        return HttpResponse(t.render(c), mimetype="text/xml")
    
    # Evaluate any counter alerts associated with this labyrinth, building a list
    # of alerts that will be sent along with the node data to the user.
    alert_messages = []
    for counter_id in request.session['counters']:
        alerts = Alert.objects.filter(counter=counter_id)
        for alert in alerts:
            if(alert.operation == '<'):
                if(request.session['counters'][alert.counter.id] < alert.operation_value):
                    alert_messages.append(alert.message)
            elif(alert.operation == '<='):
                if(request.session['counters'][alert.counter.id] <= alert.operation_value):
                    alert_messages.append(alert.message)
            elif(alert.operation == '=='):
                if(request.session['counters'][alert.counter.id] == alert.operation_value):
                    alert_messages.append(alert.message)
            elif(alert.operation == '!='):
                if(request.session['counters'][alert.counter.id] != alert.operation_value):
                    alert_messages.append(alert.message)
            elif(alert.operation == '>='):
                if(request.session['counters'][alert.counter.id] >= alert.operation_value):
                    alert_messages.append(alert.message)
            elif(alert.operation == '>'):
                if(request.session['counters'][alert.counter.id] > alert.operation_value):
                    alert_messages.append(alert.message)
    
    # Retrieve the links going out from this node and prune any
    # which shouldn't be shown through evaluation of node conditions.
    rawOutgoingLinks = Link.objects.filter(source=node)
    outgoingLinks = []
    accessedNodes = request.session['accessedNodes']
    for link in rawOutgoingLinks:
        # Prepend the icon path with the root URL, if an icon has been specified.
        if link.icon:
            link.icon = reverse('ol-root') + 'uploads/' + str(link.icon)
        
        linkedNode = Node.objects.get(id=link.destination.id)
        
        # Evaluate the condition bound to the linked node.
        try:
            nodeCondition = linkedNode.condition
            
            # See if all of the required nodes are in the list of accessed nodes.
            conditionPassed = True
            for requiredNode in nodeCondition.nodes.all():
                if not requiredNode.id in accessedNodes:
                    conditionPassed = False
                    break
            
            if(conditionPassed):
                # If the link label isn't present, use the Node's title as such
                if not(link.label):
                    link.label = link.destination.name
                
                # Add the link to the list of available actions
                outgoingLinks.append(link)
        except ObjectDoesNotExist:
            # If the link label isn't present, use the node's title as such
            if not(link.label):
                link.label = link.destination.name
            
            # Add the link to the list of available actions
            outgoingLinks.append(link)
    
    # Replace any media resource tags with their appropriate markup
    messageComponents = re.split('\[\[MR\:([0-9]+)\]\]', node.message)
    message = ''
    for index, text in enumerate(messageComponents):
        if index % 2 == 1:
            # We've found a media resource and need to substitute
            mr = Asset.objects.get(id=text)
            t = loader.get_template('asset_templates/'+mr.type+'.tpl')
            message += t.render(RequestContext(request, {'mr': mr}))
        else:
            message += text
    node.message = message
    
    # Replace any question tags with their appropriate markup
    messageComponents = re.split('\[\[QU\:([0-9]+)\]\]', node.message)
    message = ''
    for index, text in enumerate(messageComponents):
        if index % 2 == 1:
            # We've found a question and need to substitute
            question = Question.objects.get(id=text)
            t = loader.get_template('question_templates/'+question.type+'.tpl')
            message += t.render(RequestContext(request, {'question': question}))
        else:
            message += text
    node.message = message
    
    # Track this node and its counters permanently in the report database.
    nodeRecord = NodeRecord(report=request.session['report'], node=node)
    nodeRecord.save()
    
    for counter in counterData:
        counterRecord = CounterRecord(node_record=nodeRecord, 
            counter=Counter.objects.get(id=counter.get('id')), counter_value=counter.get('value'))
        counterRecord.save()
    
    # Load the template and pass the data along. If the viewer has requested
    # the classic (2.x) version use that template instead of the usual one.
    if arguments.get('mode') == 'classic':
        t = loader.get_template('node_classic.xml')
    else:
        t = loader.get_template('node.xml')
    c = RequestContext(request, {
        'node' : node,
        'links': outgoingLinks,
        'counters': counterData,
        'alerts': alert_messages,
        'avatar': node.avatar,
        'labyrinth': labyrinth,
        'siteRoot': reverse('ol-root'),
        'sessionID': request.session.session_key
    })
    return HttpResponse(t.render(c), mimetype="text/xml")

## 
## Get Report Viewer
##
## This view generates the report viewer.
## 

@initialize
def getReportViewer(request, **arguments):
    
    t = loader.get_template('report_viewer.html')
    c = RequestContext(request, {})
    return HttpResponse(t.render(c))

## 
## Read Reports
##
## This view provides a listing of all of the reports available to the user.
## 

def readReports(request):
    
    reports = Report.objects.filter(user=request.user)
    t = loader.get_template('reports.xml')
    c = RequestContext(request, {'reports': reports})
    return HttpResponse(t.render(c), mimetype="text/xml")

## 
## Read Report
##
## This view provides all of the details for a specific report.
## 

def readReport(request, **arguments):
    
    report = Report.objects.get(id=arguments.get('reportID'))
    feedbackRules = report.labyrinth.feedback_set.all()
    feedback = []
    
    # Get all of the node records associated with this report.
    nodeRecords = NodeRecord.objects.filter(report=report)
    
    # Figure out the length of the session.
    sessionDuration = nodeRecords[len(nodeRecords)-1].timestamp - nodeRecords[0].timestamp
    
    for rule in feedbackRules:
        if rule.type == 'general':
            feedback.append(rule.message)
        elif rule.type == 'time':
            if rule.comparison == '<':
                if(sessionDuration < rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '<=':
                if(sessionDuration <= rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '==':
                if(sessionDuration == rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '!=':
                if(sessionDuration != rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '>=':
                if(sessionDuration >= rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '>':
                if(sessionDuration > rule.comparison_value):
                    feedback.append(rule.message)
        elif rule.type == 'node':
            if rule.node.id in nodeRecords.values_list('node', flat=True):
                feedback.append(rule.message)
        elif rule.type == 'node_type':
            if rule.node_type == 'avoid':
                numberOfNodes = len(report.noderecord_set.filter(node__type='avoid'))
            elif rule.node_type == 'visit':
                numberOfNodes = len(report.noderecord_set.filter(node__type='visit'))
            if rule.comparison == '<':
                if(numberOfNodes < rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '<=':
                if(numberOfNodes <= rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '==':
                if(numberOfNodes == rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '!=':
                if(numberOfNodes != rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '>=':
                if(numberOfNodes >= rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '>':
                if(numberOfNodes > rule.comparison_value):
                    feedback.append(rule.message)
        elif rule.type == 'counter':
            # Get the counter value from the last node accessed in the report
            counterValue = nodeRecords[len(nodeRecords)-1].counterrecord_set.get(counter=rule.counter).counter_value
            if rule.comparison == '<':
                if(counterValue < rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '<=':
                if(counterValue <= rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '==':
                if(counterValue == rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '!=':
                if(counterValue != rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '>=':
                if(counterValue >= rule.comparison_value):
                    feedback.append(rule.message)
            elif rule.comparison == '>':
                if(counterValue > rule.comparison_value):
                    feedback.append(rule.message)
    
    t = loader.get_template('report.xml')
    c = RequestContext(request, {'feedback': feedback})
    return HttpResponse(t.render(c), mimetype="text/xml")
    

## =============================================================================
## Utility Views/Functions
## 
##   These views/functions are responsible for miscellaneous cleanup routines,
##   as well as simple navigation throughout the application.
## =============================================================================

## 
## Restart Labyrinth
##
## This view clears the session and sends the user back to the start of the labyrinth.
## 

def restartLabyrinth(request):
    
    # Grab the ID of the current labyrinth
    labyrinth_id = request.session['currentLabyrinth']
    
    # Flush all information relevant to this labyrinth
    clearSession(request)
    
    return HttpResponseRedirect(reverse('ol-root')+'labyrinth/'+labyrinth_id+'/')

## 
## Exit Labyrinth
##
## This view clears the session and sends the user back to the index view.
## 

def exitLabyrinth(request):
    
    clearSession(request)
    
    # Send the user back to the index page
    return HttpResponseRedirect(reverse('ol-root'))

## 
## Login User
##
## This view is meant as a login check; since a login can be accepted for
## any view, this is a placeholder allowing a login to be requested and a
## result to be returned, instead of a full-blown template.
## 

@initialize
def loginUser(request):
    
    if request.user.is_authenticated():
        return HttpResponse(request.session.session_key, mimetype="text/xml")
    else:
        return False

## 
## Logout User
##
## This view logs the user out and then exits the labyrinth,
## as statistics cannot be gathered across two sessions.
## 

def logoutUser(request):
    
    logout(request)
    
    # Exit the labyrinth, if required
    return exitLabyrinth(request)

## 
## Clear Session
##
## This function strips labyrinth information (ie, counters, timers)
## from the session data. Called when exiting, starting, and restarting
## a labyrinth.
## 

def clearSession(request):
    
    # If there is a node traversal path, remove it
    if('accessedNodes' in request.session):
        del request.session['accessedNodes']
    
    # Remove all counters
    if('counters' in request.session):
        del request.session['counters']
    
    # Remove any timing information left in the session
    if('startTime' in request.session):
        del request.session['startTime']
    if('timeLimit' in request.session):
        del request.session['timeLimit']
    
    # Remove the current labyrinth
    if('currentLabyrinth' in request.session):
        del request.session['currentLabyrinth']


## ============================================================================
## Session API Views
## 
##   These views are responsible for the generation and parsing of XML used to
##   create, read, update and delete (CRUD) user sessions.
## =============================================================================

## 
## Create Session
## 
## This view can be used to create a session without opening a browser, and
## returns a session ID for later reference.
## 

@initialize
def createSession(request, **arguments):
    
    # Explicitly call the session save function, which forces the instantiation
    # of a new session (only if one has not been created yet).
    request.session.save()

    # Return the newly created session's ID
    return HttpResponse(request.session.session_key, mimetype="text/xml")

## 
## Read Session
## 
## This view generates an XML representation of the requesting user's session.
## 

@initialize
def readSession(request, **arguments):
    
    if request.session.get('timeLimit') and not request.session.get('paused'):
        # This labyrinth has a time limit which should be floored
        request.session['timeRemaining'] = math.floor(request.session['timeLimit']
        - (time.time() - request.session['startTime'])) + request.session['pauseTime']
    else:
        # This labyrinth has no time limit, or is
        # paused, and a sentinel value is used
        request.session['timeRemaining'] = '--'
    
    # Merge the current counter values with their actual data and save them
    counterData = []
    for counter_id, counterValue in request.session['counters'].iteritems():
        tempCounter = {}
        tempCounterData = Counter.objects.get(id=counter_id)
        tempCounter['id'] = counter_id
        tempCounter['value'] = counterValue
        tempCounter['name'] = tempCounterData.name
        tempCounter['unit'] = tempCounterData.unit
        counterData.append(tempCounter)

    t = loader.get_template('session.xml')
    c = RequestContext(request, {
        'state': request.session,
        'counters': counterData
    })

    return HttpResponse(t.render(c), mimetype="text/xml")

## 
## Update Session
## 
## This view is responsible for parsing a "state" POST variable and pushing its
## values into the internal session bound in the initialization routine.
## 

@initialize
def updateSession(request, **arguments):
    
    # Ensure that we've got a "state" variable in the POST data header.
    if request.method == "POST" and request.POST.has_key('state'):
        
        # Pull the XML data and parse it.
        xmlString = request.POST.get('state')
        xmlData = minidom.parseString(xmlString)
        
        pauseTag = xmlData.getElementsByTagName('pause')
        
        # If there is an actual pause tag in the message, parse it and update the session.
        if len(pauseTag):
            newPauseState = int(pauseTag[0].getAttribute('state').strip())
        
            if request.session.get('paused') != newPauseState and newPauseState == 1:
                # The session is being paused
                request.session['paused'] = 1
                request.session['pauseMessage'] = pauseTag[0].getAttribute('message')
                request.session['pauseStart'] = time.time()
            elif request.session.get('paused') != newPauseState:
                # The session is being resumed
                request.session['paused'] = 0
                request.session['pauseMessage'] = ''
                request.session['pauseTime'] += math.floor(time.time() - request.session.get('pauseStart'))
                request.session['pauseStart'] = False
        
        # Iterate through the counters and update the session with whichever valid ones we find.
        for counter in xmlData.getElementsByTagName('counter'):
            
            # Do a little bit of type checking to prevent string value counters
            try:
                counterID = int(counter.getAttribute('id').strip())
                counterValue = int(counter.getAttribute('value').strip())
            except ValueError:
                continue
            
            # Ensure the counter actually existed before we go ahead and update it.
            if request.session['counters'].get(counterID) != None:
                request.session['counters'][counterID] = counterValue
        
        request.session.save()
    
    t = loader.get_template('session.xml')
    c = RequestContext(request, {
        'state': request.session
        #'counters': counterData
    })
    
    return HttpResponse(t.render(c), mimetype="text/xml")
    

## 
## Delete Session
## 
## This view can be used to delete a session, if it exists.
## 

@initialize
def deleteSession(request, **arguments):

    request.session.delete()

    return HttpResponse('')
    

## 
## Recursive Zip
## 
## This function is used to zip entire directories of files,
## and is used by the labyrinth MVP export view.
## 

def recursive_zip(archive, directory='.'):
    
    nodes = os.listdir(directory)
    for item in nodes:
        if directory != '.':
            item = directory + '/' + item
        if os.path.isfile(item):
            archive.write(item)
        elif os.path.isdir(item):
            recursive_zip(archive, item)